물리적 κ΄€μ μ—μ„œ 인덱슀 μ‚΄νŽ΄λ³΄κΈ°

@루킀 Β· January 06, 2023 Β· 25 min read

λ°μ΄ν„°λ² μ΄μŠ€ 쿼리 ν–₯상을 μ–ΈκΈ‰ν•  λ–„, μΈλ±μŠ€λŠ” 항상 빠지지 μ•Šκ³  λ“±μž₯ν•˜λŠ” ν•­λͺ© 쀑 ν•˜λ‚˜μž…λ‹ˆλ‹€. μ™œ 쿼리 ν–₯μƒμ—λŠ” μΈλ±μŠ€μ— λŒ€ν•œ μ• κΈ°κ°€ 항상 λ‚˜μ˜€λŠ” κ±ΈκΉŒμš”?? 이번 κΈ€μ—μ„œ 데이터 I/O μž‘μ—…μ— λŒ€ν•΄μ„œ 근본적으둜 μ‚΄νŽ΄λ³΄λ©΄μ„œ μΈλ±μŠ€μ— λŒ€ν•΄μ„œ 쑰금 더 μ•Œμ•„λ³΄λŠ” μ‹œκ°„μ„ 가져보렀고 ν•©λ‹ˆλ‹€.

μ €μž₯ 맀체λ₯Ό μ‚¬μš©ν•˜λŠ” 방식

μ»΄ν“¨ν„°μ˜ CPUλ‚˜ λ©”λͺ¨λ¦¬μ²˜λŸΌ 전기적 νŠΉμ„±μ„ 가지고 μžˆλŠ” μž₯치의 μ„±λŠ₯은 짧은 μ‹œκ°„ λ™μ•ˆ λΉ λ₯Έ μ†λ„λ‘œ λ°œμ „ν–ˆμ§€λ§Œ λ””μŠ€ν¬ 같은 기계식 μž₯치의 μ„±λŠ₯은 μƒλŒ€μ μœΌλ‘œ 느리게 λ°œμ „μ„ ν–ˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ 자기 λ””μŠ€ν¬ μ›νŒμ— μ˜μ‘΄ν•˜λŠ” ν•˜λ“œ λ””μŠ€ν¬μ˜ 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μ΅œκ·Όμ—λŠ” SSD λ“œλΌμ΄λΈŒλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. SSDλŠ” κΈ°μ‘΄ ν•˜λ“œ λ””μŠ€ν¬ λ“œλΌμ΄λΈŒμ—μ„œ 데이터λ₯Ό μ €μž₯ν•˜λŠ” μ›νŒμ„ μ œκ±°ν•˜κ³  ν”Œλ ˆμ‹œ λ©”λͺ¨λ¦¬λ₯Ό μž₯μ°©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. 이 λ•Œλ¬Έμ— κΈ°κ³„μ μœΌλ‘œ μ›νŒμ„ νšŒμ „μ‹œμΌœλ„ λ˜μ§€ μ•Šμ•„ μ•„μ£Ό 빨리 데이터λ₯Ό μ½μ–΄μ˜¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

μ΄λŸ¬ν•œ 데이터λ₯Ό μ½μ–΄μ˜€λŠ” μž‘μ—…μ€ 순차 I/O와 랜덀 I/O둜 ꡬ뢄을 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ””μŠ€ν¬μ˜ 헀더λ₯Ό 움직이지 μ•Šκ³  ν•œ λ²ˆμ— λ§Žμ€ 데이터λ₯Ό μ½λŠ” 순차 I/Oμ—μ„œλŠ” SSD와 ν•˜λ“œ λ””μŠ€ν¬ λ“œλΌμ΄λ²„μ˜ μ„±λŠ₯ 차이가 거의 λ°œμƒν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ μ‹€μ œ λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„μ˜ μž‘μ—… λΉ„μ€‘μ—μ„œ ν•œ λ²ˆμ— λ§Žμ€ μ–‘μ˜ 데이터λ₯Ό μ½λŠ” 비쀑은 크지가 μ•ŠμŠ΅λ‹ˆλ‹€. λŒ€λΆ€λΆ„μ˜ λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„μ˜ μž‘μ—…μ€ μž‘μ€ 데이터λ₯Ό 읽고 μ“°λŠ” μž‘μ—…μ΄λ©°, μ΄λŸ¬ν•œ μž‘μ—…μ€ 랜덀 I/Oλ₯Ό ν†΅ν•΄μ„œ μˆ˜ν–‰λ˜κ²Œ λ©λ‹ˆλ‹€. 랜덀 I/Oκ°€ λ°œμƒν•˜λ©΄ κ·Έ 만큼 λ””μŠ€ν¬ ν—€λ”μ˜ μœ„μΉ˜ 이동이 많이 λ°œμƒν•˜κΈ° λ•Œλ¬Έμ—, λ””μŠ€ν¬ ν—€λ”μ˜ 이동이 μ—†λŠ” SSD λ“œλΌμ΄λ²„κ°€ 훨씬 쒋은 μ„±λŠ₯을 λ‚Έλ‹€κ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

img

랜덀 I/O와 순차 I/O

랜덀 I/O λΌλŠ” ν‘œν˜„μ€ ν•˜λ“œ λ””μŠ€ν¬ λ“œλΌμ΄λ²„μ˜ μ›νŒμ„ λŒλ €μ„œ 읽어야 ν•  데이터가 μ €μ •λœ μœ„μΉ˜λ‘œ λ””μŠ€ν¬ 헀더λ₯Ό μ΄λ™μ‹œν‚¨ λ‹€μŒ 데이터λ₯Ό μ½λŠ” 것을 μ˜λ―Έν•˜λŠ”λ°, 순차 I/O λ˜ν•œ μ›λ¦¬λŠ” κ°™μŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ 차이가 μƒκΈ°λŠ” 점은 순차 I/OλŠ” 3개의 νŽ˜μ΄μ§€λ₯Ό λ””μŠ€ν¬μ— μ°ΎκΈ° μœ„ν•΄ 1번 μ‹œμŠ€ν…œ μ½œμ„ μš”μ²­ν–ˆμ§€λ§Œ, 랜덀 I/OλŠ” 3개의 νŽ˜μ΄μ§€λ₯Ό μ°ΎκΈ° μœ„ν•΄ 3번 μ‹œμŠ€ν…œ μ½œμ„ μš”μ²­ν•œλ‹€λŠ” μ μž…λ‹ˆλ‹€. 즉, 순차 I/Oκ°€ 랜덀 I/O에 λΉ„ν•΄μ„œ μ•½ 3λ°° 정도 λΉ λ₯΄λ‹€κ³  ν•  수 있으며, λ””μŠ€ν¬μ˜ μ„±λŠ₯은 λ””μŠ€ν¬ ν—€λ”μ˜ μœ„μΉ˜ 이동 없이 μ–Όλ§ˆλ‚˜ λ§Žμ€ 데이터λ₯Ό ν•œ λ²ˆμ— 읽고 μ“°λŠ”κ°€μ— 따라 κ²°μ •λœλ‹€κ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

SSDμ—μ„œλŠ” λ””μŠ€ν¬ μ›νŒμ„ 가지지 μ•ŠκΈ° λ•Œλ¬Έμ— 순차 I/O와 랜덀 I/Oκ°€ 차이가 없을 κ²ƒμœΌλ‘œ λ³΄μ΄μ§€λ§Œ, SSD λ“œλΌμ΄λ²„μ—μ„œλ„ 랜덀 I/OλŠ” 순차 I/O에 λΉ„ν•΄ 전체 μ²˜λ¦¬λŸ‰μ΄ λ–¨μ–΄μ§‘λ‹ˆλ‹€.

μ—¬κΈ°μ„œ μš°λ¦¬λŠ” 인덱슀λ₯Ό μ‚¬μš©ν•˜λŠ” λͺ©μ μ„ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. 사싀 쿼리λ₯Ό νŠœλ‹ν•΄μ„œ 랜덀 I/Oλ₯Ό 순차 I/O둜 λ³€κ²½ν•΄μ„œ μ‹€ν–‰ν•  방법은 거의 μ—†λ‹€κ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 즉, 랜덀 I/Oλ₯Ό 순차 I/O둜 λ³€κ²½ν•˜μ—¬ μ„±λŠ₯을 ν–₯μƒμ‹œν‚€λŠ” 것이 μ•„λ‹ˆλΌ 랜덀 I/Oλ₯Ό μ€„μ—¬μ„œ λ°μ΄ν„°λ² μ΄μŠ€ μ„±λŠ₯을 ν–₯μƒμ‹œν‚€λŠ” 것이 λͺ©μ μ΄λ©°, λͺ©μ μ„ 이루기 μœ„ν•œ μˆ˜λ‹¨μœΌλ‘œ 인덱슀λ₯Ό μ‚¬μš©ν•œλ‹€κ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


μΈλ±μŠ€κ°€ μˆ˜ν–‰λ˜λŠ” 원리

μΈλ±μŠ€λŠ” 검색 속도λ₯Ό λΉ λ₯΄κ²Œ ν•˜κΈ° μœ„ν•΄μ„œ λ°μ΄ν„°λ² μ΄μŠ€μ˜ 곡간에 νŠΉμ • μ»¬λŸΌλ“€μ„ μ •λ ¬ν•˜λŠ” 자료ꡬ쑰라고 ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ„œ 저희가 μ‚΄νŽ΄λ³΄μ•„μ•Ό ν•  점은 β€˜λ°μ΄ν„°λ² μ΄μŠ€ 곡간에 νŠΉμ • μ»¬λŸΌλ“€μ„ μ •λ ¬ν•œλ‹€β€™ μž…λ‹ˆλ‹€. 값듀이 항상 μ •λ ¬λ˜μ–΄ 있기 λ•Œλ¬Έμ— 찾고자 ν•˜λŠ” 값을 λΉ λ₯΄κ²Œ μ°Ύμ•„μ˜¬ 수 μžˆλŠ” 것 μž…λ‹ˆλ‹€. μ΄λŸ¬ν•œ μΈλ±μŠ€λŠ” InnoDBμ—μ„œλŠ” B-TreeλΌλŠ” 자료ꡬ쑰λ₯Ό ν†΅ν•΄μ„œ 각 데이터듀을 κ΄€λ¦¬ν•©λ‹ˆλ‹€.

B-Tree 인덱슀

일반적으둜 InnoDBμ—μ„œλŠ” B-Tree 기반의 μ•Œκ³ λ¦¬μ¦˜μ„ μ‚¬μš©ν•œ 인덱슀λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. (μ •ν™•ν•˜κ²ŒλŠ” 쑰금 λ³€ν˜•λœ B+Treeλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€)

일반적으둜 B-TreeλŠ” 트리 ꡬ쑰의 μ΅œμƒλ‹¨μ— 루트 λ…Έλ“œκ°€ μ‘΄μž¬ν•˜κ³ , κ·Έ ν•˜μœ„μ— μžμ‹ λ…Έλ“œλ“€μ΄ λΆ™μ–΄ μžˆλŠ” ν˜•νƒœμž…λ‹ˆλ‹€. μ—¬κΈ°μ„œ κ°€μž₯ ν•˜μœ„μ— μžˆλŠ” μžμ‹ λ…Έλ“œλ₯Ό 리프 λ…Έλ“œλΌκ³  λͺ…μΉ­ν•˜λŠ”λ°, 리프 λ…Έλ“œλŠ” 항상 μ‹€μ œ 데이터 λ ˆμ½”λ“œλ₯Ό μ°Ύμ•„κ°€κΈ° μœ„ν•œ μ£Όμ†Œκ°’μ„ 가지고 μžˆμŠ΅λ‹ˆλ‹€. 특히 InnoDBμ—μ„œλŠ” 기본적으둜 ν…Œμ΄λΈ”μ˜ λ ˆμ½”λ“œλ“€μ΄ PK의 μˆœμ„œλŒ€λ‘œ μ •λ ¬λ˜μ–΄ μ €μž₯되기 λ•Œλ¬Έμ— 물리적으둜 ν…Œμ΄λΈ” λ ˆμ½”λ“œλ“€μ΄ μ €μž₯λœλ‹€κ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ΄λ ‡κ²Œ 트리 ν˜•νƒœλ‘œ κ΅¬μΆ•λœ 인덱슀λ₯Ό κ²€μƒ‰ν•˜λŠ” 과정을 트리 탐색이라고 λΆ€λ₯΄λŠ”데, μ΅œμƒλ‹¨ 루트 λ…Έλ“œμ—μ„œ μ›ν•˜λŠ” 값이 μžˆλŠ” 리프 λ…Έλ“œλ₯Ό μ°ΎλŠ” λ°©μ‹μœΌλ‘œ λ™μž‘μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€.

인덱슀λ₯Ό ν†΅ν•œ λ ˆμ½”λ“œ 읽기

인덱슀λ₯Ό 톡해 ν…Œμ΄λΈ”μ˜ λ ˆμ½”λ“œλ₯Ό μ½λŠ” 것은 인덱슀λ₯Ό 거치치 μ•Šκ³  λ°”λ‘œ ν…Œμ΄λΈ”μ˜ λ ˆμ½”λ“œλ₯Ό μ½λŠ” 것보닀 높은 λΉ„μš©μ΄ λ“œλŠ” μž‘μ—…μž…λ‹ˆλ‹€. (랜덀 I/O둜 λ™μž‘ν•˜κΈ° λ•Œλ¬Έ)

예λ₯Ό λ“€μ–΄, ν…Œμ΄λΈ”μ— λ ˆμ½”λ“œκ°€ 100만 건이 μ €μž₯λ˜μ–΄ μžˆλ‹€κ³  κ°€μ •ν•˜κ³ , κ·Έ μ€‘μ—μ„œ 50만 건을 읽어야 ν•˜λŠ” 쿼리가 μžˆλ‹€κ³  가정을 ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. 전체 ν…Œμ΄λΈ”μ„ λͺ¨λ‘ 순차적으둜 μ½μ–΄μ„œ ν•„μš” μ—†λŠ” 50만 건을 λ²„λ¦¬λŠ” 것이 νš¨μœ¨μ μΈμ§€, 인덱슀λ₯Ό 톡해 λžœλ€ν•˜κ²Œ μ½μ–΄μ„œ 50만 건만 읽어 μ˜€λŠ” 것이 νš¨μœ¨μ μΈμ§€λ₯Ό νŒλ‹¨ν•  수 μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.

일반적으둜 DBMS의 μ˜΅ν‹°λ§ˆμ΄μ €μ—μ„œλŠ” 인덱슀λ₯Ό 톡해 λ ˆμ½”λ“œ 1건을 μ½λŠ” 것이 ν…Œμ΄λΈ”μ—μ„œ 직접 λ ˆμ½”λ“œ 1건을 μ½λŠ” 것보닀 4 ~ 5λ°° 정도 λΉ„μš©μ΄ 더 많이 λ“€μ–΄κ°„λ‹€κ³  ν•©λ‹ˆλ‹€. 즉, 인덱슀λ₯Ό 톡해 읽어야 ν•  λ ˆμ½”λ“œμ˜ κ±΄μˆ˜κ°€ 전체 ν…Œμ΄λΈ” λ ˆμ½”λ“œμ˜ 20 ~ 25%λ₯Ό λ„˜μ–΄μ„œλ©΄ 인덱슀λ₯Ό μ΄μš©ν•˜μ§€ μ•Šκ³  ν…Œμ΄λΈ”μ„ λͺ¨λ‘ 직접 μ½λŠ” λ°©μ‹μœΌλ‘œ μ²˜λ¦¬ν•˜λŠ”κ²Œ 효율적이라고 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


B-Tree μžλ£Œκ΅¬μ‘°μ—μ„œμ˜ 인덱슀 μˆ˜ν–‰ 방법

인덱슀 λ ˆμΈμ§€ μŠ€μΊ”

인덱슀 λ ˆμΈμ§€ μŠ€μΊ”μ€ 검색해야 ν•  인덱슀의 λ²”μœ„κ°€ κ²°μ •λ˜μ—ˆμ„ λ•Œ μ‚¬μš©ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€. 인덱슀 μ ‘κ·Ό 방법 κ°€μš΄λ° κ°€μž₯ λŒ€ν‘œμ μΈ μ ‘κ·Ό λ°©μ‹μœΌλ‘œ, 인덱슀 μ‚¬μš©μ‹œ μ΅œμ ν™”λœ 방법 쀑 ν•˜λ‚˜λΌκ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

루트 λ…Έλ“œμ—μ„œ 찾고자 ν•˜λŠ” 졜초의 리프 λ…Έλ“œλ₯Ό νƒμƒ‰ν•œ ν›„, ν•΄λ‹Ή 리프 λ…Έλ“œμ™€ μ—°κ²°λœ 리프 λ…Έλ“œλ₯Ό μ„ ν˜• νƒμƒ‰ν•˜λŠ” λ°©λ²•μœΌλ‘œ λ™μž‘ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 인덱슀 λ ˆμΈμ§€ μŠ€μΊ” 방식은 크게 2가지 λ°©λ²•μœΌλ‘œ λΆ„λ₯˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

  • μ‹€μ œ 인덱슀 λ°μ΄ν„°λ§Œ μ½λŠ” 경우
  • μ‹€μ œ 데이터 파일의 λ ˆμ½”λ“œλ₯Ό 읽어와야 ν•˜λŠ” 경우

μœ„μ˜ μ‹€μ œ 데이터 파일의 λ ˆμ½”λ“œλ₯Ό 읽어와야 ν•˜λŠ” κ²½μš°μ—λŠ” 랜덀 λ””μŠ€ν¬ I/Oκ°€ λ°œμƒν•˜κ²Œ λ©λ‹ˆλ‹€. λ ˆμ½”λ“œ ν•œ 건 λ‹¨μœ„λ‘œ 랜덀 I/Oκ°€ λ°œμƒν•˜κΈ° λ•Œλ¬Έμ— 이λ₯Ό μœ μ˜ν•΄μ„œ 인덱슀λ₯Ό μ„€μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€.

img 1

λ§Œμ•½ μ΄λ ‡κ²Œ 인덱슀λ₯Ό μ‚¬μš©ν•˜λ©΄μ„œλ„ 데이터 파일의 λ ˆμ½”λ“œλ₯Ό μ½μ–΄μ˜€μ§€ μ•Šλ„λ‘ ν•˜κΈ° μœ„ν•΄μ„œλŠ” 컀버링 μΈλ±μŠ€λΌλŠ” 방법을 μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€. 찾고자 ν•˜λŠ” λ ˆμ½”λ“œμ˜ 값듀을 μΈλ±μŠ€μ— μ €μž₯ν•˜κΈ° λ•Œλ¬Έμ— 랜덀 I/Oκ°€ 쀄어듀고 μ‘°νšŒμ— λŒ€ν•œ μ„±λŠ₯ 이점을 κ°€μ Έκ°ˆ μˆ˜κ°€ μžˆμŠ΅λ‹ˆλ‹€.

인덱슀 ν’€ μŠ€μΊ”

인덱슀 λ ˆμΈμ§€ μŠ€μΊ”κ³Ό λ§ˆμ°¬κ°€μ§€λ‘œ 인덱슀λ₯Ό μ‚¬μš©ν•˜μ§€λ§Œ 인덱슀 λ ˆμΈμ§€ μŠ€μΊ”κ³ΌλŠ” 달리 인덱슀의 μ²˜μŒλΆ€ν„° λκΉŒμ§€ λͺ¨λ‘ μ½λŠ” 방식을 인덱슀 ν’€ μŠ€μΊ”μ΄λΌκ³  ν•©λ‹ˆλ‹€. 일반적으둜 인덱슀의 ν¬κΈ°λŠ” ν…Œμ΄λΈ”μ˜ 크기보닀 μž‘μœΌλ―€λ‘œ 직접 ν…Œμ΄λΈ”μ„ μ²˜μŒλΆ€ν„° λκΉŒμ§€ μ½λŠ” κ²ƒλ³΄λ‹€λŠ” 인덱슀만 μ½λŠ” 것이 νš¨μœ¨μ μž…λ‹ˆλ‹€.

단, 인덱슀뿐만 μ•„λ‹ˆλΌ 데이터 λ ˆμ½”λ“œκΉŒμ§€ λͺ¨λ‘ 읽어야 ν•œλ‹€λ©΄ μ ˆλŒ€ 이 λ°©μ‹μœΌλ‘œ μ²˜λ¦¬κ°€ λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 쿼리가 μΈλ±μŠ€μ— λͺ…μ‹œλœ 칼럼만으둜 쑰건을 μ²˜λ¦¬ν•  수 μžˆλŠ” κ²½μš°μ— 주둜 이 방식이 μ‚¬μš©λ©λ‹ˆλ‹€.

img 2

μœ„μ˜ 그림을 보면 인덱슀의 리프 λ…Έλ“œμ˜ 제일 μ•ž(λ’€)둜 μ΄λ™ν•œ ν›„, μ—°κ²°λ˜μ–΄ μžˆλŠ” 리프 λ…Έλ“œλ“€μ„ λ”°λΌμ„œ μ²˜μŒλΆ€ν„° λκΉŒμ§€ νƒμƒ‰ν•˜λŠ” λ°©μ‹μœΌλ‘œ λ™μž‘ν•˜κ²Œ λ©λ‹ˆλ‹€. μ΄λŠ” 인덱슀 λ ˆμΈμ§€ μŠ€μΊ”λ³΄λ‹€λŠ” λΉ λ₯΄μ§„ μ•Šμ§€λ§Œ ν…Œμ΄λΈ” ν’€ μŠ€μΊ”λ³΄λ‹€λŠ” 훨신 적은 λ””μŠ€ν¬ I/Oκ°€ λ°œμƒν•˜κΈ° λ•Œλ¬Έμ— 효율적이라고 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


닀쀑 컬럼 인덱슀

μ‹€μ œ μ„œλΉ„μŠ€μš© λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œλŠ” 2개 μ΄μƒμ˜ μΉΌλŸΌμ„ ν¬ν•¨ν•˜λŠ” μΈλ±μŠ€κ°€ 더 많이 μ‚¬μš©λ©λ‹ˆλ‹€. 이λ₯Ό 닀쀑 컬럼 인덱슀라고 λΆ€λ¦…λ‹ˆλ‹€. 닀쀑 컬럼 μΈλ±μŠ€κ°€ μ €μž₯λ˜λŠ” κ΄€μ μ—μ„œ 이λ₯Ό μ‚΄νŽ΄λ³΄λ©΄ μ•žμ˜ μ»¬λŸΌμ— μ˜μ‘΄ν•΄μ„œ 정렬이 μ΄λ£¨μ–΄μ§€λŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. 즉, 닀쀑 칼럼 μΈλ±μŠ€μ—μ„œλŠ” 인덱슀 λ‚΄μ—μ„œ 각 컬럼의 μœ„μΉ˜κ°€ 맀우 μ€‘μš”ν•˜λ‹€λŠ” 것을 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

img 3


InnoDBμ—μ„œμ˜ 인덱슀 μ •λ ¬ 및 μŠ€μΊ” λ°©ν–₯

인덱슀λ₯Ό 생성할 λ•Œ κ°œλ°œμžκ°€ μ„€μ •ν•œ μ •λ ¬ κ·œμΉ™μ— λ”°λΌμ„œ 인덱슀의 ν‚€ 값은 항상 μ˜€λ¦„μ°¨μˆœμ΄λ‚˜ λ‚΄λ¦Όμ°¨μˆœμœΌλ‘œ μ •λ ¬λ˜μ–΄ μ €μž₯λ©λ‹ˆλ‹€. ν•˜μ§€λ§Œ μΈλ±μŠ€κ°€ μ •λ ¬λœ κ²ƒκ³ΌλŠ” λ‹€λ₯΄κ²Œ 인덱슀λ₯Ό μ½λŠ” 곳의 μœ„μΉ˜μ— λ”°λΌμ„œ μ½λŠ” λ°©ν–₯이 λ‹¬λΌμ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ 인덱슀λ₯Ό μ–΄λŠ λ°©ν–₯으둜 μ½μœΌμ§€λŠ” 각 쿼리에 λ”°λΌμ„œ μ˜΅ν‹°λ§ˆμ΄μ €μ˜ μ‹€ν–‰ κ³„νšμ— 따라 κ²°μ •λœλ‹€κ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μΈλ±μŠ€λŠ” MySQL 5.7 μ΄ν•˜ λ²„μ „μ—μ„œλŠ” 항상 μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬λ˜μ–΄ μžˆμ—ˆμ§€λ§Œ, 인덱슀λ₯Ό μ΅œλŒ“κ°’λΆ€ν„° 거꾸둜 읽으면 λ‚΄λ¦Όμ°¨μˆœμœΌλ‘œ 값을 κ°€μ Έμ˜€κ²Œ λ©λ‹ˆλ‹€. λ‹€μŒ 그림을 ν†΅ν•΄μ„œλ„ 이λ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

img 4

즉, 인덱슀 생성 μ‹œμ μ— μ˜€λ¦„μ°¨μˆœ λ˜λŠ” λ‚΄λ¦Όμ°¨μˆœμœΌλ‘œ 정렬이 κ²°μ •λ˜μ§€λ§Œ 쿼리가 인덱슀λ₯Ό μ‚¬μš©ν•˜λŠ” μ‹œμ μ— 인덱슀λ₯Ό μ½λŠ” λ°©ν–₯에 따라 μ˜€λ¦„μ°¨μˆœ λ˜λŠ” λ‚΄λ¦Όμ°¨μˆœ μ •λ ¬ 효과λ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€.

μ—¬κΈ°μ„œ λ†€λΌμš΄ 점은 데이터 쑰회 μ‹œ, μ˜€λ¦„μ°¨μˆœκ³Ό λ‚΄λ¦Όμ°¨μˆœμ˜ μ„±λŠ₯차이가 λ°œμƒν•œλ‹€λŠ” μ μž…λ‹ˆλ‹€. μ±… Real MySQL 8.0의 λ‚΄μš©μ—μ„œλ„ μ•Œ 수 μžˆλ“―μ΄, 데이터가 1μ²œλ§Œκ±΄μ„ κΈ°μ€€μœΌλ‘œ μ‘°νšŒν•˜μ˜€μ„ λ•Œ, μ˜€λ¦„μ°¨μˆœ 정렬이 λ‚΄λ¦Όμ°¨μˆœ 정렬보닀 μ„±λŠ₯이 μ’‹λ‹€λŠ” 것을 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

img 5

ν•˜μ§€λ§Œ 이뢀뢄은 이해가 잘 λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. InnoDBλŠ” 각 νŽ˜μ΄μ§€ κ°„μ˜ 더블 λ§ν¬λ“œ 리슀트λ₯Ό ν†΅ν•΄μ„œ 전진과 후진을 ν•  수 있기 λ•Œλ¬Έμž…λ‹ˆλ‹€. 이 λ•Œλ¬Έμ— ν•΄λ‹Ή λ‹¨μ„œλ§ŒμœΌλ‘œλŠ” μ™œ μ„±λŠ₯차이가 λ°œμƒν•˜λŠ”μ§€ 잘 이해가 λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.

μ±…κ³Ό 검색을 톡해 μ°Ύμ•„λ³Έ κ²°κ³Ό μ‹€μ œ InnoDBμ—μ„œλŠ” λ‹€μŒ 2가지 이유 λ•Œλ¬Έμ— ν›„μ§„μœΌλ‘œ κ²€μƒ‰ν•˜λŠ”κ²Œ λŠλ¦¬λ‹€λŠ” 것을 μ•Œ 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

  • νŽ˜μ΄μ§€ 잠금이 Forward Index Scan에 μ ν•©ν•œ ꡬ쑰
  • νŽ˜μ΄μ§€ λ‚΄μ—μ„œ 인덱슀 λ ˆμ½”λ“œλŠ” 단방ν–₯으둜만 μ—°κ²°λœ ꡬ쑰 (Forwarded Single Linked Link)

νŽ˜μ΄μ§€ 잠금이 Forward Index Scan에 μ ν•©ν•œ ꡬ쑰

λ¨Όμ € Forward Index Scan에 μ ν•©ν•œ ꡬ쑰에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.

InnoDB의 νŽ˜μ΄μ§€ 잠금 방식은 Forward Index Scan을 μ€‘μ‹¬μœΌλ‘œ κ΅¬ν˜„λ˜μ–΄ μžˆλŠ”λ°, ν•΄λ‹Ή λ°©μ‹μœΌλ‘œ 인덱슀 리프 νŽ˜μ΄μ§€λ₯Ό 읽을 λ•ŒλŠ” λ°‘μ˜ μ½”λ“œμ™€ 같이 Forward Scan μˆœμ„œλŒ€λ‘œ νŽ˜μ΄μ§€μ˜ μž κΈˆμ„ κ±Έκ³  ν•΄μ œν•˜λŠ” μˆœμ„œλ‘œ λ™μž‘ν•˜κ²Œ λ©λ‹ˆλ‹€.

void btr_pcur_move_to_next_page(
/*=======================*/
    btr_pcur_t* cursor, /*!< in: persistent cursor; must be on the
                last record of the current page */
    mtr_t*      mtr)    /*!< in: mtr */
{
    // ... skip ...

    page = btr_pcur_get_page(cursor);
    next_page_no = btr_page_get_next(page, mtr);

    // ... skip ...
    
    buf_block_t*    block = btr_pcur_get_block(cursor);

    // λ‹€μŒ νŽ˜μ΄μ§€λ₯Ό μ°Ύμ•„μ„œ, 잠금 νšλ“
    next_block = btr_block_get(
        page_id_t(block->page.id.space(), next_page_no),
        block->page.size, mode,
        btr_pcur_get_btr_cur(cursor) -> index, mtr);

    next_page = buf_block_get_frame(next_block);

    // ... skip ...
    
    // λ‹€μŒ νŽ˜μ΄μ§€ 잠금 νšλ“ ν›„, ν˜„μž¬ νŽ˜μ΄μ§€μ˜ μž κΈˆμ„ ν•΄μ œ
    btr_leaf_page_release(btr_pcur_get_block(cursor), mode, mtr);

    // ... skip ...
}

κ·Έλ ‡λ‹€λ©΄ Backward Index Scanμ‹œ νŽ˜μ΄μ§€ μž κΈˆμ„ νšλ“ν•˜λŠ” μ½”λ“œλŠ” κ³Όμ—° μ–΄λ–€ λ°©λ²•μœΌλ‘œ λ™μž‘μ„ ν•˜κ²Œ λ κΉŒμš”?? λ‹€μŒ μ½”λ“œλ₯Ό 보면 κ·Έ 차이λ₯Ό μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

void btr_pcur_move_backward_from_page(
/*=============================*/
        btr_pcur_t*     cursor, /*!< in: persistent cursor, must be on the first
                                record of the current page */
        mtr_t*          mtr)    /*!< in: mtr */
{
    // ... skip ...
    // μ»€μ„œμ˜ ν˜„μž¬ μƒνƒœ λ°±μ—…
    btr_pcur_store_position(cursor, mtr);

    mtr_commit(mtr);  // Mini-transaction 컀밋 (νŽ˜μ΄μ§€ 잠금 ν•΄μ œ)

    mtr_start(mtr);   // Mini-transaction μ‹œμž‘

    // BTR_SEARCH_PREV λͺ¨λ“œλ‘œ μ»€μ„œ 볡ꡬ
    btr_pcur_restore_position(latch_mode2, cursor, mtr);

    page = btr_pcur_get_page(cursor);

    prev_page_no = btr_page_get_prev(page, mtr);

    /* For intrinsic table we don't do optimistic restore and so there is
       no left block that is pinned that needs to be released. */
    if (!dict_table_is_intrinsic(
         btr_cur_get_index(btr_pcur_get_btr_cur(cursor)) -> table)) {

        if (prev_page_no == FIL_NULL) {
        } else if (btr_pcur_is_before_first_on_page(cursor)) {

            prev_block = btr_pcur_get_btr_cur(cursor) -> left_block;
            // λΆˆν•„μš”μ‹œ ν˜„μž¬ νŽ˜μ΄μ§€ 잠금 ν•΄μ œ
            btr_leaf_page_release(btr_pcur_get_block(cursor), latch_mode, mtr);

            page_cur_set_after_last(prev_block, btr_pcur_get_page_cur(cursor));
         } else {
            /* The repositioned cursor did not end on an infimum
               record on a page. Cursor repositioning acquired a latch
               also on the previous page, but we do not need the latch:
               release it. */

            prev_block = btr_pcur_get_btr_cur(cursor) -> left_block;
            // λΆˆν•„μš”μ‹œ 이전 νŽ˜μ΄μ§€ 잠금 ν•΄μ œ
            btr_leaf_page_release(prev_block, latch_mode, mtr);
        }
    }

    cursor -> latch_mode = latch_mode;
    cursor -> old_stored = false;
}

μ½”λ“œμ˜ 양이 λ„ˆλ¬΄ λ§Žμ•„ 100%λŠ” μ΄ν•΄λŠ” λͺ»ν–ˆμ§€λ§Œ λŒ€λž΅ μ½”λ“œμ˜ 흐름을 μ‚΄νŽ΄λ³΄λ©΄ λ‹€μŒκ³Ό κ°™λ‹€λŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

  1. μ»€μ„œμ˜ μƒνƒœλ₯Ό μ €μž₯ν•˜κ³  λ‚΄λΆ€μ˜ λ―Έλ‹ˆ νŠΈλžœμž­μ…˜μ„ μ»€λ°‹ν•΄μ„œ λ―Έλ‹ˆ νŠΈλžœμž­μ…˜ 버퍼λ₯Ό 리두 둜그 λ²„νΌλ‘œ 볡사
  2. λ―Έλ‹ˆ νŠΈλžœμž­μ…˜μ„ μž¬μ‹œμž‘
  3. μ»€μ„œμ˜ μƒνƒœλ₯Ό λ‹€μ‹œ 볡ꡬ

κ²°κ΅­ ν•΄λ‹Ή μ½”λ“œλ₯Ό λ³΄μ•˜μ„ λ•Œ, InnoDB의 리프 νŽ˜μ΄μ§€λŠ” 더블 λ§ν¬λ“œ 리슀트둜 μ—°κ²°λ˜μ–΄ 있기 λ•Œλ¬Έμ—, μ–΄λŠ λ°©ν–₯μœΌλ‘œλ“  μ‘°νšŒλŠ” κ°€λŠ₯ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ νŽ˜μ΄μ§€ 잠금 κ³Όμ •μ—μ„œ λ°λ“œλ½μ„ λ°©μ§€ν•˜κΈ° μœ„ν•΄μ„œ B-Tree의 μ™Όμͺ½μ—μ„œ 였λ₯Έμͺ½ μˆœμ„œλ‘œλ§Œ μž κΈˆμ„ νšλ“ν•˜λ„λ‘ ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. 이 λ•Œλ¬Έμ— Forward Index Scan에 λΉ„ν•΄μ„œ Backward Index Scan이 훨씬 λ§Žμ€ λ³΅μž‘ν•œ 과정을 μˆ˜ν–‰ν•΄μ—¬ λ¦¬μ†ŒμŠ€κ°€ 많이 λ“ λ‹€λŠ” 것을 μ•Œ μˆ˜κ°€ μžˆμŠ΅λ‹ˆλ‹€.

νŽ˜μ΄μ§€ λ‚΄μ—μ„œ 인덱슀 λ ˆμ½”λ“œλŠ” 단방ν–₯으둜만 μ—°κ²°λœ ꡬ쑰

2번째 이유인 νŽ˜μ΄μ§€ λ‚΄μ—μ„œ 인덱슀 λ ˆμ½”λ“œλŠ” 단방ν–₯으둜만 μ—°κ²°λœ κ΅¬μ‘°λΌλŠ” 뢀뢄에 λŒ€ν•΄μ„œ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

InnoDB μŠ€ν† λ¦¬μ§€ 엔진이 νŠΉμ • λ ˆμ½”λ“œλ₯Ό 검색할 λ•Œ, B-Treeλ₯Ό μ΄μš©ν•΄μ„œ 검색 λŒ€μƒ λ ˆμ½”λ“œκ°€ μ €μž₯된 νŽ˜μ΄μ§€λ₯Ό κ²€μƒ‰ν•˜κ²Œ λ©λ‹ˆλ‹€. 이 νŽ˜μ΄μ§€μ—λŠ” μˆ˜λ§Žμ€ λ ˆμ½”λ“œκ°€ μ €μž₯되게 λ˜λŠ”λ°, 일반적으둜 600개 μ΄μƒμ˜ λ ˆμ½”λ“œ(ν‚€λ‹Ή 20 Byte)κ°€ μ €μž₯될 수 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ 600개의 λ ˆμ½”λ“œλ₯Ό 맀번 κ²€μƒ‰ν•˜λŠ” 것은 속도λ₯Ό μ €ν•˜μ‹œν‚€λŠ” μž‘μ—…μ΄λΌ ν•  수 μžˆμŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ InnoDBλŠ” ν•˜λ‚˜μ˜ νŽ˜μ΄μ§€μ—μ„œ μ •λ ¬λœ λ ˆμ½”λ“œ 4~8개 정도λ₯Ό λ¬Άμ–΄ λ³„λ„μ˜ 리슀트둜 κ΄€λ¦¬ν•˜μ—¬ 이λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 리슀트λ₯Ό Page Directory라고 ν•©λ‹ˆλ‹€.

img 6

μœ„μ˜ 그림을 ν†΅ν•΄μ„œ 보면 InnoDB μŠ€ν† λ¦¬μ§€ 엔진은 ν•˜λ‚˜μ˜ νŽ˜μ΄μ§€μ—μ„œ νŠΉμ • ν‚€λ₯Ό 검색할 λ•Œ Page Directoryλ₯Ό λ°”μ΄λ„ˆλ¦¬ μ„œμΉ˜ λ°©μ‹μœΌλ‘œ κ²€μƒ‰ν•˜λ©° 검색 λŒ€μƒ ν‚€λ₯Ό ν¬ν•¨ν•˜λŠ” λŒ€ν‘œν‚€λ₯Ό κ²€μƒ‰ν•˜λŠ” λ°©λ²•μœΌλ‘œ μž‘μ—…μ„ μˆ˜ν–‰ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. 그리고 λŒ€ν‘œ ν‚€λ₯Ό 찾으면 인덱슀 ν‚€ κ°’ μˆœμ„œλŒ€λ‘œ μ—°κ²°λœ Linked Listλ₯Ό μ΄μš©ν•΄μ„œ λŒ€μƒ λ ˆμ½”λ“œλ₯Ό κ²€μƒ‰ν•˜κ²Œ λ©λ‹ˆλ‹€.

μ—¬κΈ°μ„œ μœ μ˜ν•΄μ„œ λ΄μ•Όν•˜λŠ” 점은 B-Tree 리프 νŽ˜μ΄μ§€ κ΅¬μ‘°μ™€λŠ” λ‹€λ₯΄κ²Œ, νŽ˜μ΄μ§€ λ‚΄λΆ€μ˜ λ ˆμ½”λ“œλ“€μ€ Single Linked List ꡬ쑰둜 κ΅¬μ„±λ˜μ–΄ μžˆλ‹€λŠ” μ μž…λ‹ˆλ‹€. 이 λ•Œλ¬Έμ— Backward Index Scan 방식을 μ‚¬μš©ν•˜κ²Œ 되면 좔가적인 검색 μž‘μ—…μ΄ ν•„μš”ν•΄ μ˜€λ²„ν—€λ“œκ°€ λ°œμƒν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

μ΅œμ’… μ •λ¦¬ν•˜λ©΄..?

κ²°κ΅­ InnoDB의 리프 νŽ˜μ΄μ§€λŠ” 더블 λ§ν¬λ“œ 리슀트λ₯Ό 톡해 μ–‘λ°©ν–₯ 검색이 κ°€λŠ₯ν•˜μ§€λ§Œ, μ‹€μ œ λ ˆμ½”λ“œλ₯Ό μ €μž₯λ˜λŠ” λ‹¨μœ„μΈ νŽ˜μ΄μ§€ λ‚΄λΆ€μ—μ„œ 검색을 μˆ˜ν–‰ν•  λ•ŒλŠ” μ‹±κΈ€ λ§ν¬λ“œ 리슀트이기 λ•Œλ¬Έμ— Backward Index Scan에 λŒ€ν•œ μ˜€λ²„ν—€λ“œκ°€ λ°œμƒν•œλ‹€λŠ” 것 μž…λ‹ˆλ‹€.

μ•žμ—μ„œλ„ λͺ‡λ²ˆ μ–ΈκΈ‰ν–ˆμ§€λ§Œ InnoDBλŠ” λ‚΄λΆ€μ μœΌλ‘œ νŽ˜μ΄μ§€μ˜ λ ˆμ½”λ“œλ₯Ό μ ‘κ·Όν•  λ•Œλ§ˆλ‹€, νŽ˜μ΄μ§€μ— λŒ€ν•΄μ„œ μž κΈˆμ„ κ±°λŠ” λ°©μ‹μœΌλ‘œ λ™μž‘ν•©λ‹ˆλ‹€. μ΄λ•Œ Mutexλ₯Ό μ‚¬μš©ν•˜μ—¬ μž κΈˆμ„ μˆ˜ν–‰ν•˜λ©° 읽기 쿼리듀끼리도 νŽ˜μ΄μ§€ μž κΈˆμ„ μ μœ ν•˜κΈ° μœ„ν•΄μ„œ κ²½μŸν•˜κΈ° λ•Œλ¬Έμ—, ν•΄λ‹Ή νŽ˜μ΄μ§€μ— λ™μ‹œμ— μ ‘κ·Όν•˜λŠ” λ©€ν‹° μŠ€λ ˆλ“œκ°€ λ§Žμ•„μ§ˆμˆ˜λ‘ μ„±λŠ₯ 영ν–₯λ„λŠ” λ”μš± μ»€μ§€κ²Œ λœλ‹€κ³  ν•  수 μžˆμ„ κ²ƒμœΌλ‘œ λ³΄μž…λ‹ˆλ‹€.

마무리

μ§€κΈˆκΉŒμ§€ μΈλ±μŠ€μ— λŒ€ν•΄μ„œ μ‚΄νŽ΄λ³΄λ©΄μ„œ 인덱슀λ₯Ό μ™œ μ‚¬μš©ν•΄μ•Ό ν•˜λŠ”μ§€, 인덱슀 μžλ£Œκ΅¬μ‘°μ™€ μˆ˜ν–‰λ°©λ²•μ€ μ–΄λ– ν•œ 것듀이 μžˆλŠ”μ§€, 물리적 κ΄€μ μ—μ„œ μΈλ±μŠ€λŠ” μ–΄λ–»κ²Œ λ™μž‘ν•˜λŠ”μ§€λ₯Ό μ‚΄νŽ΄λ³΄λŠ” μ‹œκ°„μ„ κ°€μ‘ŒμŠ΅λ‹ˆλ‹€. ν”„λ‘œμ νŠΈλ₯Ό μ§„ν–‰ν•˜λ©΄μ„œ 인덱슀λ₯Ό μ μš©ν•΄ λ³΄μ•˜μ§€λ§Œ, λ‹¨μˆœνžˆ 검색 μ„±λŠ₯ ν–₯상을 μœ„ν•΄μ„œ μ μš©ν•œ 츑면이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 이번 λ‚΄μš©μ„ μ •λ¦¬ν•˜λ©΄μ„œ 쑰금 더 μΈλ±μŠ€μ— λŒ€ν•΄ μ•Œκ²Œλœ 것 κ°™μ•„μ„œ 재미있게 ν•΄λ‹Ή λ‚΄μš©μ„ 정리할 수 μžˆμ—ˆλ˜ 것 κ°™μŠ΅λ‹ˆλ‹€.

μ°Έκ³ 

@루킀
μ‹œκ°„μ΄ ν˜λŸ¬λ„ 변화에 λŒ€μ‘λ˜λŠ” μ½”λ“œλ₯Ό μΆ”κ΅¬ν•©λ‹ˆλ‹€.